' nitroserver.bmx

' 2006.9.9 cgi-bin process launching support
' 2006.9.7 post handling - reads _data until contentsize=0

' enctype=multipart/form-data

Strict

Local socket:TSocket=CreateTCPSocket()
BindSocket socket,4663
SocketListen socket

Print "nitrohost listening on port 4663"

While True
	Local client:TSocket=SocketAccept(socket,20)
	If client New TConnection.Create(client)
	TConnection.PollAll
Wend

End

Type TByteBuffer Extends TStream
	Field	bytes:Byte[]
	Field	readpointer

	Method Read( buf:Byte Ptr,count )
		If count>readpointer count=readpointer
		If count=0 Return
		MemCopy buf,bytes,count
		readpointer:-count
		If readpointer MemMove bytes,Varptr bytes[count],readpointer
		Return count 
	End Method

	Method Write( buf:Byte Ptr,count )
		Local	n,m
		n=readpointer+count
		If n>bytes.length
			m=Max(bytes.length*1.5,n)
			bytes=bytes[..m]
		EndIf
		MemCopy Varptr bytes[readpointer],buf,count
		readpointer=n
		Return count
	End Method	
	
	Method LineAvail()
		Local	i
		For i=0 Until readpointer
			If bytes[i]=10 Return True
		Next
	End Method

	Method FlushBytes:Byte[]()
		Local res:Byte[]=bytes[..readpointer]
		readpointer=0
		Return res
	End Method
End Type


Type TConnection

	Global connections:TList=New TList

	Field id
	Field _socket:TSocket
	Field _stream:TStream
	Field _data$

	Field _getname$
	Field _postname$

	Field _header:TMap	
	Field _content:Byte[]
	Field _contentsize
	Field _contentpos
	Field _contentboundary$
		
	Function PollAll()
		Local connect:TConnection
		For connect=EachIn connections
			connect.Poll
		Next
	End Function		

	Method Create:TConnection(client:TSocket)
		DebugLog "creating connection!"
		_socket=client
		_stream=CreateSocketStream(client)
		connections.AddLast Self
		id=connections.count()
		Return Self
	End Method

' process control

	Field _process:TProcess
	Field _replybuffer:TByteBuffer
	Field _reply$

	Method RunProcess(proc$)
		Local cmd$		
'		If _contentboundary
'			Local l=Len _contentboundary+4
'			_content=_content[l..(Len _content)-l-3]
'		EndIf
		Local f:tstream=WriteFile("content.txt")
		f.write _content,_content.length
		CloseFile f
	
		putenv_ "CONTENT_LENGTH="+_content.length
		
		cmd="cgi-bin"+proc.Trim()+".exe"	' +_content.length
		_process=CreateProcess(cmd$,HIDECONSOLE)
		If Not _process DebugLog "Process Failure with "+cmd;Return
		If Not _process.status() DebugLog "Process Failed to start "+cmd;Return		
'		WritePipe content$
		_replybuffer=New TByteBuffer
		_reply$=""
		
		PollProcess
	End Method
	
	Method PollProcess()
		Local pipe:TPipeStream
		Local status
		Local count
		Local bytes:Byte[],line$

		If Not _process Return		
		pipe=_process.pipe
' write to process
		count=_contentsize-_contentpos
		If count
			DebugLog "pipe.write count="+count
			count=pipe.write(Byte Ptr(_content)+_contentpos,count)
			DebugLog "pipe.write count="+count
			_contentpos:+count
			If _contentpos=_contentsize 
				_content=Null
				pipe.write("~r~n",2)
				pipe.flush()
				DebugLog "flushed pipe _content=null!"
			EndIf
		EndIf
' read status BEFORE pipe
		status=_process.status()
' read from process
		bytes=pipe.ReadPipe()
		If Len bytes
			_reply:+string.FromBytes(bytes,Len bytes)
		EndIf
Rem		
		
		If bytes _replybuffer.WriteBytes bytes,Len bytes		
		DebugLog "reply bytes="+Len bytes
		While _replybuffer.LineAvail()
			line$=_replybuffer.ReadLine()
			DebugLog "reply line:"+line
			_reply:+line
		Wend
EndRem
' refresh status
		If status=0
			DebugLog "process complete"
			_process.terminate
			_process.close
			_process=Null
			_content=Null
			If _reply
				WritePage _reply,Len _reply
			EndIf
		EndIf

	End Method
	
' virtual server activation from polled http connection

	Method ConfirmPost(postname$,_header:TMap)
		Local v$,t$,b$,p
		v$=String(_header.ValueForKey("Content-Length"))
		DebugLog "contentsize:"+v
		t$=String(_header.ValueForKey("Content-Type"))
		p=t.find("boundary=")
		If p<>-1 _contentboundary=t[p+9..]
		DebugLog "contenttype:"+t+" _contentboundary="+_contentboundary
'contenttype: multipart/form-data; boundary=---------------------------7d61b5111055c
		_contentsize=Int(v.trim())
		Return True
	End Method

	Method PostContent(uri$)
		Local a$
		DebugLog "Post:"+uri
		For a$=EachIn _header.keys()
'			DebugLog "+"+a+"="+String(_header.ValueForKey(a))
		Next
'		DebugLog "Content:"+content
		RunProcess uri
		_contentpos=0
	End Method

	Method WritePage(html$,size)
		WriteLine _stream,"HTTP/1.1 200 OK"
		WriteLine _stream,"Content-Type: text/html; charset=UTF-8"
		WriteLine _stream,"Content-Length: "+size
		WriteLine _stream,"Pragma: no-cache"
		WriteLine _stream,"Expires: Fri, 01 Jan 1990 00:00:00 GMT"
		WriteLine _stream,"Cache-control: no-cache, no-store, must-revalidate"
		WriteLine _stream,"Connection: keep-alive"
		WriteLine _stream,"Keep-Alive: 30000"
		WriteLine _stream,""
		WriteLine _stream,html
	End Method	
	
	Method WriteStyle(css$,size)
		WriteLine _stream,"HTTP/1.1 200 OK"
		WriteLine _stream,"Content-Type: text/css; charset=UTF-8"
		WriteLine _stream,"Content-Length: "+size
		WriteLine _stream,"Pragma: no-cache"
		WriteLine _stream,"Expires: Fri, 01 Jan 1990 00:00:00 GMT"
		WriteLine _stream,"Cache-control: no-cache, no-store, must-revalidate"
		WriteLine _stream,"Connection: keep-alive"
		WriteLine _stream,"Keep-Alive: 30000"
		WriteLine _stream,""
		WriteLine _stream,css
	End Method	

	Method ServePage(uri$)
		Local file$,size,p
		
		If uri$="/" 
			file="index.html"
		Else
			file=uri
			If file[..1]="/" file=file[1..]
			p=file.find("?")
			If p>0 file=file[..p]
'			file=uri[..p]
'			uri=args[p..]
		EndIf
		
		file="www/"+file

		If file And FileType(file)=FILETYPE_FILE
			size=FileSize(file)
			If size=0
				DebugLog "ServePage "+uri+" is empty"
				Return
			EndIf	
			
			Select ExtractExt(file).tolower()
			Case "css"
				DebugLog "serving "+uri+" css from connection#"+id
				WriteStyle LoadText(file),size
			Default
				DebugLog "serving "+uri+" txt from connection#"+id
				WritePage LoadText(file),size
			End Select
			
			
'			DebugLog file+" sent"
		Else
			DebugLog "Failed To find file "+file
		EndIf		
'		debuglog "Served!"
	End Method

' local state machine http state machine

	Method getline$()
		Local p,l$
		p=_data.find("~n")
		If p=-1 p=_data.length
		l=_data[..p]
		_data=_data[p+1..]		
		Return l
	End Method

	Method addheader(mess$)
		Local p=mess.find(":")
		If p<>-1
			_header.insert mess[..p],mess[p+1..]
		Else
			DebugLog "Unknown header line :"+mess
		EndIf
	End Method
		
	Method Poll()
		Local bytes,mess$

		If _process
			PollProcess
			Return
		EndIf

		If _content
			bytes=SocketReadAvail(_socket)
			If bytes And _contentpos<_contentsize
				bytes=_stream.Read(Byte Ptr(_content)+_contentpos,bytes)
				_contentpos:+bytes
				DebugLog "_contentpos="+_contentpos
			EndIf
			If _contentpos=_contentsize
				PostContent _postname
				_postname=""
			EndIf
			Return
		EndIf

		bytes=SocketReadAvail(_socket)
		If bytes
			_data:+ReadString(_stream,bytes)
'			DebugLog "_data:"+_data
		EndIf
			
		While _data
			mess=Trim(getline())
			If _getname
				If mess=""
					ServePage _getname
					_getname=""
				Else
					addheader mess
					Continue
				EndIf
			EndIf
			If _postname
				If mess=""
					If ConfirmPost(_postname,_header)
						If _contentsize _content=New Byte[_contentsize]
						_contentpos=Min(_contentsize,Len _data)
						If _contentpos
							Local src:Byte Ptr=_data.toCString()
							Local dest:Byte Ptr=Byte Ptr(_content)
							MemCopy dest,src,_contentpos
							MemFree src
						EndIf
					EndIf
					_data=_data[_contentsize..]
				Else
					addheader mess
				EndIf
				Continue
			EndIf
			If mess[..4]="GET "
				_getname=mess[4..]
				_getname=_getname.replace(" HTTP/1.1","")
				_header=New TMap
			EndIf			
			If mess[..4]="POST"
				_postname=mess[4..]
				_postname=_postname.replace(" HTTP/1.1","")
				_header=New TMap
				_contentsize=0
				_content=Null
				DebugLog "post="+_postname
			EndIf			
		Wend	
	End Method

End Type

Rem

AUTH-TYPE

- Tells the type of authentication used if the web server supports it 
and the script is protected. An example of authentication may be a 
something like a password.


CONTENT-LENGTH

- When your form method is "post," the data in the form is sent in 
one long string or characters. The CONTENT-LENGTH tells how long the 
string is in bytes (characters). An example of CONTENT-LENGTH: 256 bytes


CONTENT-TYPE

- You can send many kinds of data to a script. This variable tells 
what kind of data it is from a list of MIME types. 
A sample of CONTENT-TYPE: x-www-form-urlencoded (tells the script
that this data came from a WWW form...)


GATEWAY_INTERFACE

- Tells the script what version of the CGI specification is being 
used. As with anything in the computer world, CGI is being updated 
and every change makes a new version; the higher the number, the 
newer the version. An example of GATEWAY_INTERFACE : 1.1


PATH_INFO

- Gives any information that may be included in a URL in addition 
to the name of the script, such as extra options.


PATH_TRANSLATED

- Contains information on where the script resides; it lists the 
directories, the script name, and any other information


QUERY_STRING

- This contains the information that follows the name of a script. 
It often contains information that the script can use to send you 
where you want to go, like a search engine. If you search for a 
topic in a search engine, like Infoseek, it will send you to an 
index of pages. In the location box, after the URL and ?, you will 
sometimes see some words separated with +s and other characters. 
This is the query string and this is what is contained in QUERY_STRING.


REMOTE_ADDR

- Contains the IP address of a visitor who is using a script. 
An example of REMOTE_ADDR: 246.74.105.3


REMOTE_HOST

- Contains the name of the computer of a visitor who is using a script. 
An example of REMOTE_HOST: Excelsior.


REMOTE_USER

- Tells a script the name of the person using it.
An example of REMOTE_USER: nhiemann


SCRIPT_NAME

- Contains the immediate location of a script from the cgi-bin directory. 
An example of SCRIPT_NAME: /cgi-bin/myscript.cgi


SERVER_NAME

- Gives the name or IP address of the computer on which the web server is running.


SERVER_PORT

- Gives the port number on which the web server is waiting for requests. 
An example of SERVER_PORT: 80.


SERVER_PROTOCOL

- Contains information on whatever protocol and its version number 
that the Web server is using. 
An example of SERVER_PROTOCOL: HTTP/2.0.


SERVER_SOFTWARE

- Gives the name and version number of the Web server that called 
the script. An example of SERVER_SOFTWARE: APACHE/1.2b4


HTTP_ACCEPT

- Contains information on the types of information the visitor's web 
browser can display, often it is one or more of the MIME types. 
An example of HTTP_ACCEPT: image/gif,image/jpeg


HTTP_REFERER

- Tells the script from what form in what page the data is coming. 
An example of HTTP_REFERER: http://www.ourpage.com/comments.html


HTTP_USER_AGENT

- Tells the script what browser and version of that browser your 
visitor is using. An example of HTTP_USER_AGENT: Mozilla/2.0(Windows;32bit) 

EndRem



Rem
DebugLog:_data:

POST /terminal HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/msword, application/vnd.ms-powerpoint, application/vnd.ms-excel, */*
Referer: http://localhost:4663/
Accept-Language: en-nz
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NET CLR 1.1.4322; .NET CLR 2.0.50727)
Host: localhost:4663
Content-Length: 0
Connection: Keep-Alive
Cache-Control: no-cache
EndRem
	


Rem

/favicon.ico


RemoteDOM - FunBasic goes Live...

Accepts connections from household browsers and 
provides virtual display and input devices for
a BlitzMax server application.

MaxGui applications can use RemoteDom to present
standard MaxGui interfaces to multiple remote 
users, operating in the comfort of their own web 
browser.

A single Max2D server application can maintain
multiple remote player connections and pipe 
Max2D graphics and sound automajically to a 
suprisingly powerful Dynamic HTML rendering 
engine at other end of each connection.

EndRem

Rem
Host: 127.0.0.1:4663
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.1) Gecko/20060111 Firefox/1.5.0.1
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive

EndRem

Rem
Local wsocket:TSocket=CreateTCPSocket()
ConnectSocket wsocket,HostIp("127.0.0.1"),4664
Local stream:TStream=CreateSocketStream(wsocket)
WriteLine stream,"GET / HTTP/1.1~n"


While True
	Local n=SocketReadAvail(wsocket)
	If n
		Print ReadString(stream,n)
	EndIf
	Delay 100
Wend


End

OpenURL "http://127.0.0.1:4663"
'OpenURL "http://127.0.0.1:4664"	'Google Desktop
'OpenURL "127.0.0.1"

Input "what the?"


EndRem



Rem
*GET / HTTP/1.1
Host: 127.0.0.1:4663
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.1) Gecko/20060111 Firefox/1.5.0.1
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-aliveEndRem


HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Pragma: no-cache
Expires: Fri, 01 Jan 1990 00:00:00 GMT
Cache-control: no-cache, no-store, must-revalidate



$POST / HTTP/1.1$
$Host: 127.0.0.1:4663$
$User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.1) Gecko/20060111 Firefox/1.5.0.1$
$Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5$
$Accept-Language: en-us,en;q=0.5$
$Accept-Encoding: gzip,deflate$
$Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7$
$Keep-Alive: 300$
$Connection: keep-alive$
$Referer: http://127.0.0.1:4663/$
$Content-Type: application/x-www-form-urlencoded$
$Content-Length: 0$
$$










EndRem

'	Const head$="<html><head><title>NetGui: Invalid Request</title></head>"
'	Const page$="<body>Hello World<button name=~qpush me~q></body></html>~n~n"


Rem
	Method WritePipe(l$)
		Local pipe:TPipeStream
		If Not _process Return		
		pipe=_process.pipe
		pipe.WriteLine(l$)
	End Method
EndRem
